/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <assert.h>
#include <io.h>

#include "uv.h"
#include "internal.h"
#include "handle-inl.h"
#include "req-inl.h"

static const GUID uv_msafd_provider_ids[UV_MSAFD_PROVIDER_COUNT] = {
    { 0xe70f1aa0, 0xab8b, 0x11cf,
        { 0x8c, 0xa3, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 } },
    { 0xf9eab0c0, 0x26d4, 0x11d0,
        { 0xbb, 0xbf, 0x00, 0xaa, 0x00, 0x6c, 0x34, 0xe4 } },
    { 0x9fc48064, 0x7298, 0x43e4,
        { 0xb7, 0xbd, 0x18, 0x1f, 0x20, 0x89, 0x79, 0x2a } }
};

typedef struct uv_single_fd_set_s {
    unsigned int fd_count;
    SOCKET fd_array[1];
} uv_single_fd_set_t;

static OVERLAPPED overlapped_dummy_;
static uv_once_t overlapped_dummy_init_guard_ = UV_ONCE_INIT;

static AFD_POLL_INFO afd_poll_info_dummy_;

static void uv__init_overlapped_dummy(void)
{
    HANDLE event;

    event = CreateEvent(NULL, TRUE, TRUE, NULL);
    if (event == NULL)
        uv_fatal_error(GetLastError(), "CreateEvent");

    memset(&overlapped_dummy_, 0, sizeof overlapped_dummy_);
    overlapped_dummy_.hEvent = (HANDLE)((uintptr_t)event | 1);
}

static OVERLAPPED* uv__get_overlapped_dummy()
{
    uv_once(&overlapped_dummy_init_guard_, uv__init_overlapped_dummy);
    return &overlapped_dummy_;
}

static AFD_POLL_INFO* uv__get_afd_poll_info_dummy()
{
    return &afd_poll_info_dummy_;
}

static void uv__fast_poll_submit_poll_req(uv_loop_t* loop, uv_poll_t* handle)
{
    uv_req_t* req;
    AFD_POLL_INFO* afd_poll_info;
    DWORD result;

    /* Find a yet unsubmitted req to submit. */
    if (handle->submitted_events_1 == 0) {
        req = &handle->poll_req_1;
        afd_poll_info = &handle->afd_poll_info_1;
        handle->submitted_events_1 = handle->events;
        handle->mask_events_1 = 0;
        handle->mask_events_2 = handle->events;
    } else if (handle->submitted_events_2 == 0) {
        req = &handle->poll_req_2;
        afd_poll_info = &handle->afd_poll_info_2;
        handle->submitted_events_2 = handle->events;
        handle->mask_events_1 = handle->events;
        handle->mask_events_2 = 0;
    } else {
        /* Just wait until there's an unsubmitted req. */
        /* This will happen almost immediately as one of the 2 outstanding */
        /* requests is about to return. When this happens, */
        /* uv__fast_poll_process_poll_req will be called, and the pending */
        /* events, if needed, will be processed in a subsequent request. */
        return;
    }

    /* Setting Exclusive to TRUE makes the other poll request return if there */
    /* is any. */
    afd_poll_info->Exclusive = TRUE;
    afd_poll_info->NumberOfHandles = 1;
    afd_poll_info->Timeout.QuadPart = INT64_MAX;
    afd_poll_info->Handles[0].Handle = (HANDLE)handle->socket;
    afd_poll_info->Handles[0].Status = 0;
    afd_poll_info->Handles[0].Events = 0;

    if (handle->events & UV_READABLE) {
        afd_poll_info->Handles[0].Events |= AFD_POLL_RECEIVE | AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT | AFD_POLL_ABORT;
    } else {
        if (handle->events & UV_DISCONNECT) {
            afd_poll_info->Handles[0].Events |= AFD_POLL_DISCONNECT;
        }
    }
    if (handle->events & UV_WRITABLE) {
        afd_poll_info->Handles[0].Events |= AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL;
    }

    memset(&req->u.io.overlapped, 0, sizeof req->u.io.overlapped);

    result = uv_msafd_poll((SOCKET)handle->peer_socket,
        afd_poll_info,
        afd_poll_info,
        &req->u.io.overlapped);
    if (result != 0 && WSAGetLastError() != WSA_IO_PENDING) {
        /* Queue this req, reporting an error. */
        SET_REQ_ERROR(req, WSAGetLastError());
        uv_insert_pending_req(loop, req);
    }
}

static int uv__fast_poll_cancel_poll_req(uv_loop_t* loop, uv_poll_t* handle)
{
    AFD_POLL_INFO afd_poll_info;
    DWORD result;

    afd_poll_info.Exclusive = TRUE;
    afd_poll_info.NumberOfHandles = 1;
    afd_poll_info.Timeout.QuadPart = INT64_MAX;
    afd_poll_info.Handles[0].Handle = (HANDLE)handle->socket;
    afd_poll_info.Handles[0].Status = 0;
    afd_poll_info.Handles[0].Events = AFD_POLL_ALL;

    result = uv_msafd_poll(handle->socket,
        &afd_poll_info,
        uv__get_afd_poll_info_dummy(),
        uv__get_overlapped_dummy());

    if (result == SOCKET_ERROR) {
        DWORD error = WSAGetLastError();
        if (error != WSA_IO_PENDING)
            return error;
    }

    return 0;
}

static void uv__fast_poll_process_poll_req(uv_loop_t* loop, uv_poll_t* handle,
    uv_req_t* req)
{
    unsigned char mask_events;
    AFD_POLL_INFO* afd_poll_info;

    if (req == &handle->poll_req_1) {
        afd_poll_info = &handle->afd_poll_info_1;
        handle->submitted_events_1 = 0;
        mask_events = handle->mask_events_1;
    } else if (req == &handle->poll_req_2) {
        afd_poll_info = &handle->afd_poll_info_2;
        handle->submitted_events_2 = 0;
        mask_events = handle->mask_events_2;
    } else {
        assert(0);
        return;
    }

    /* Report an error unless the select was just interrupted. */
    if (!REQ_SUCCESS(req)) {
        DWORD error = GET_REQ_SOCK_ERROR(req);
        if (error != WSAEINTR && handle->events != 0) {
            handle->events = 0; /* Stop the watcher */
            handle->poll_cb(handle, uv_translate_sys_error(error), 0);
        }

    } else if (afd_poll_info->NumberOfHandles >= 1) {
        unsigned char events = 0;

        if ((afd_poll_info->Handles[0].Events & (AFD_POLL_RECEIVE | AFD_POLL_DISCONNECT | AFD_POLL_ACCEPT | AFD_POLL_ABORT)) != 0) {
            events |= UV_READABLE;
            if ((afd_poll_info->Handles[0].Events & AFD_POLL_DISCONNECT) != 0) {
                events |= UV_DISCONNECT;
            }
        }
        if ((afd_poll_info->Handles[0].Events & (AFD_POLL_SEND | AFD_POLL_CONNECT_FAIL)) != 0) {
            events |= UV_WRITABLE;
        }

        events &= handle->events & ~mask_events;

        if (afd_poll_info->Handles[0].Events & AFD_POLL_LOCAL_CLOSE) {
            /* Stop polling. */
            handle->events = 0;
            if (uv__is_active(handle))
                uv__handle_stop(handle);
        }

        if (events != 0) {
            handle->poll_cb(handle, 0, events);
        }
    }

    if ((handle->events & ~(handle->submitted_events_1 | handle->submitted_events_2)) != 0) {
        uv__fast_poll_submit_poll_req(loop, handle);
    } else if ((handle->flags & UV__HANDLE_CLOSING) && handle->submitted_events_1 == 0 && handle->submitted_events_2 == 0) {
        uv_want_endgame(loop, (uv_handle_t*)handle);
    }
}

static int uv__fast_poll_set(uv_loop_t* loop, uv_poll_t* handle, int events)
{
    assert(handle->type == UV_POLL);
    assert(!(handle->flags & UV__HANDLE_CLOSING));
    assert((events & ~(UV_READABLE | UV_WRITABLE | UV_DISCONNECT)) == 0);

    handle->events = events;

    if (handle->events != 0) {
        uv__handle_start(handle);
    } else {
        uv__handle_stop(handle);
    }

    if ((handle->events & ~(handle->submitted_events_1 | handle->submitted_events_2)) != 0) {
        uv__fast_poll_submit_poll_req(handle->loop, handle);
    }

    return 0;
}

static int uv__fast_poll_close(uv_loop_t* loop, uv_poll_t* handle)
{
    handle->events = 0;
    uv__handle_closing(handle);

    if (handle->submitted_events_1 == 0 && handle->submitted_events_2 == 0) {
        uv_want_endgame(loop, (uv_handle_t*)handle);
        return 0;
    } else {
        /* Cancel outstanding poll requests by executing another, unique poll */
        /* request that forces the outstanding ones to return. */
        return uv__fast_poll_cancel_poll_req(loop, handle);
    }
}

static SOCKET uv__fast_poll_create_peer_socket(HANDLE iocp,
    WSAPROTOCOL_INFOW* protocol_info)
{
    SOCKET sock = 0;

    sock = WSASocketW(protocol_info->iAddressFamily,
        protocol_info->iSocketType,
        protocol_info->iProtocol,
        protocol_info,
        0,
        WSA_FLAG_OVERLAPPED);
    if (sock == INVALID_SOCKET) {
        return INVALID_SOCKET;
    }

    if (!SetHandleInformation((HANDLE)sock, HANDLE_FLAG_INHERIT, 0)) {
        goto error;
    };

    if (CreateIoCompletionPort((HANDLE)sock,
            iocp,
            (ULONG_PTR)sock,
            0)
        == NULL) {
        goto error;
    }

    return sock;

error:
    closesocket(sock);
    return INVALID_SOCKET;
}

static SOCKET uv__fast_poll_get_peer_socket(uv_loop_t* loop,
    WSAPROTOCOL_INFOW* protocol_info)
{
    int index, i;
    SOCKET peer_socket;

    index = -1;
    for (i = 0; (size_t)i < ARRAY_SIZE(uv_msafd_provider_ids); i++) {
        if (memcmp((void*)&protocol_info->ProviderId,
                (void*)&uv_msafd_provider_ids[i],
                sizeof protocol_info->ProviderId)
            == 0) {
            index = i;
        }
    }

    /* Check if the protocol uses an msafd socket. */
    if (index < 0) {
        return INVALID_SOCKET;
    }

    /* If we didn't (try) to create a peer socket yet, try to make one. Don't */
    /* try again if the peer socket creation failed earlier for the same */
    /* protocol. */
    peer_socket = loop->poll_peer_sockets[index];
    if (peer_socket == 0) {
        peer_socket = uv__fast_poll_create_peer_socket(loop->iocp, protocol_info);
        loop->poll_peer_sockets[index] = peer_socket;
    }

    return peer_socket;
}

static DWORD WINAPI uv__slow_poll_thread_proc(void* arg)
{
    uv_req_t* req = (uv_req_t*)arg;
    uv_poll_t* handle = (uv_poll_t*)req->data;
    unsigned char reported_events;
    int r;
    uv_single_fd_set_t rfds, wfds, efds;
    struct timeval timeout;

    assert(handle->type == UV_POLL);
    assert(req->type == UV_POLL_REQ);

    if (handle->events & UV_READABLE) {
        rfds.fd_count = 1;
        rfds.fd_array[0] = handle->socket;
    } else {
        rfds.fd_count = 0;
    }

    if (handle->events & UV_WRITABLE) {
        wfds.fd_count = 1;
        wfds.fd_array[0] = handle->socket;
        efds.fd_count = 1;
        efds.fd_array[0] = handle->socket;
    } else {
        wfds.fd_count = 0;
        efds.fd_count = 0;
    }

    /* Make the select() time out after 3 minutes. If select() hangs because */
    /* the user closed the socket, we will at least not hang indefinitely. */
    timeout.tv_sec = 3 * 60;
    timeout.tv_usec = 0;

    r = select(1, (fd_set*)&rfds, (fd_set*)&wfds, (fd_set*)&efds, &timeout);
    if (r == SOCKET_ERROR) {
        /* Queue this req, reporting an error. */
        SET_REQ_ERROR(&handle->poll_req_1, WSAGetLastError());
        POST_COMPLETION_FOR_REQ(handle->loop, req);
        return 0;
    }

    reported_events = 0;

    if (r > 0) {
        if (rfds.fd_count > 0) {
            assert(rfds.fd_count == 1);
            assert(rfds.fd_array[0] == handle->socket);
            reported_events |= UV_READABLE;
        }

        if (wfds.fd_count > 0) {
            assert(wfds.fd_count == 1);
            assert(wfds.fd_array[0] == handle->socket);
            reported_events |= UV_WRITABLE;
        } else if (efds.fd_count > 0) {
            assert(efds.fd_count == 1);
            assert(efds.fd_array[0] == handle->socket);
            reported_events |= UV_WRITABLE;
        }
    }

    SET_REQ_SUCCESS(req);
    req->u.io.overlapped.InternalHigh = (DWORD)reported_events;
    POST_COMPLETION_FOR_REQ(handle->loop, req);

    return 0;
}

static void uv__slow_poll_submit_poll_req(uv_loop_t* loop, uv_poll_t* handle)
{
    uv_req_t* req;

    /* Find a yet unsubmitted req to submit. */
    if (handle->submitted_events_1 == 0) {
        req = &handle->poll_req_1;
        handle->submitted_events_1 = handle->events;
        handle->mask_events_1 = 0;
        handle->mask_events_2 = handle->events;
    } else if (handle->submitted_events_2 == 0) {
        req = &handle->poll_req_2;
        handle->submitted_events_2 = handle->events;
        handle->mask_events_1 = handle->events;
        handle->mask_events_2 = 0;
    } else {
        assert(0);
        return;
    }

    if (!QueueUserWorkItem(uv__slow_poll_thread_proc,
            (void*)req,
            WT_EXECUTELONGFUNCTION)) {
        /* Make this req pending, reporting an error. */
        SET_REQ_ERROR(req, GetLastError());
        uv_insert_pending_req(loop, req);
    }
}

static void uv__slow_poll_process_poll_req(uv_loop_t* loop, uv_poll_t* handle,
    uv_req_t* req)
{
    unsigned char mask_events;
    int err;

    if (req == &handle->poll_req_1) {
        handle->submitted_events_1 = 0;
        mask_events = handle->mask_events_1;
    } else if (req == &handle->poll_req_2) {
        handle->submitted_events_2 = 0;
        mask_events = handle->mask_events_2;
    } else {
        assert(0);
        return;
    }

    if (!REQ_SUCCESS(req)) {
        /* Error. */
        if (handle->events != 0) {
            err = GET_REQ_ERROR(req);
            handle->events = 0; /* Stop the watcher */
            handle->poll_cb(handle, uv_translate_sys_error(err), 0);
        }
    } else {
        /* Got some events. */
        int events = req->u.io.overlapped.InternalHigh & handle->events & ~mask_events;
        if (events != 0) {
            handle->poll_cb(handle, 0, events);
        }
    }

    if ((handle->events & ~(handle->submitted_events_1 | handle->submitted_events_2)) != 0) {
        uv__slow_poll_submit_poll_req(loop, handle);
    } else if ((handle->flags & UV__HANDLE_CLOSING) && handle->submitted_events_1 == 0 && handle->submitted_events_2 == 0) {
        uv_want_endgame(loop, (uv_handle_t*)handle);
    }
}

static int uv__slow_poll_set(uv_loop_t* loop, uv_poll_t* handle, int events)
{
    assert(handle->type == UV_POLL);
    assert(!(handle->flags & UV__HANDLE_CLOSING));
    assert((events & ~(UV_READABLE | UV_WRITABLE)) == 0);

    handle->events = events;

    if (handle->events != 0) {
        uv__handle_start(handle);
    } else {
        uv__handle_stop(handle);
    }

    if ((handle->events & ~(handle->submitted_events_1 | handle->submitted_events_2)) != 0) {
        uv__slow_poll_submit_poll_req(handle->loop, handle);
    }

    return 0;
}

static int uv__slow_poll_close(uv_loop_t* loop, uv_poll_t* handle)
{
    handle->events = 0;
    uv__handle_closing(handle);

    if (handle->submitted_events_1 == 0 && handle->submitted_events_2 == 0) {
        uv_want_endgame(loop, (uv_handle_t*)handle);
    }

    return 0;
}

int uv_poll_init(uv_loop_t* loop, uv_poll_t* handle, int fd)
{
    return uv_poll_init_socket(loop, handle, (SOCKET)uv__get_osfhandle(fd));
}

int uv_poll_init_socket(uv_loop_t* loop, uv_poll_t* handle,
    uv_os_sock_t socket)
{
    WSAPROTOCOL_INFOW protocol_info;
    int len;
    SOCKET peer_socket, base_socket;
    DWORD bytes;
    DWORD yes = 1;

    /* Set the socket to nonblocking mode */
    if (ioctlsocket(socket, FIONBIO, &yes) == SOCKET_ERROR)
        return uv_translate_sys_error(WSAGetLastError());

        /* Try to obtain a base handle for the socket. This increases this chances */
        /* that we find an AFD handle and are able to use the fast poll mechanism. */
        /* This will always fail on windows XP/2k3, since they don't support the */
        /* SIO_BASE_HANDLE ioctl. */
#ifndef NDEBUG
    base_socket = INVALID_SOCKET;
#endif

    if (WSAIoctl(socket,
            SIO_BASE_HANDLE,
            NULL,
            0,
            &base_socket,
            sizeof base_socket,
            &bytes,
            NULL,
            NULL)
        == 0) {
        assert(base_socket != 0 && base_socket != INVALID_SOCKET);
        socket = base_socket;
    }

    uv__handle_init(loop, (uv_handle_t*)handle, UV_POLL);
    handle->socket = socket;
    handle->events = 0;

    /* Obtain protocol information about the socket. */
    len = sizeof protocol_info;
    if (getsockopt(socket,
            SOL_SOCKET,
            SO_PROTOCOL_INFOW,
            (char*)&protocol_info,
            &len)
        != 0) {
        return uv_translate_sys_error(WSAGetLastError());
    }

    /* Get the peer socket that is needed to enable fast poll. If the returned */
    /* value is NULL, the protocol is not implemented by MSAFD and we'll have */
    /* to use slow mode. */
    peer_socket = uv__fast_poll_get_peer_socket(loop, &protocol_info);

    if (peer_socket != INVALID_SOCKET) {
        /* Initialize fast poll specific fields. */
        handle->peer_socket = peer_socket;
    } else {
        /* Initialize slow poll specific fields. */
        handle->flags |= UV_HANDLE_POLL_SLOW;
    }

    /* Initialize 2 poll reqs. */
    handle->submitted_events_1 = 0;
    uv_req_init(loop, (uv_req_t*)&(handle->poll_req_1));
    handle->poll_req_1.type = UV_POLL_REQ;
    handle->poll_req_1.data = handle;

    handle->submitted_events_2 = 0;
    uv_req_init(loop, (uv_req_t*)&(handle->poll_req_2));
    handle->poll_req_2.type = UV_POLL_REQ;
    handle->poll_req_2.data = handle;

    return 0;
}

int uv_poll_start(uv_poll_t* handle, int events, uv_poll_cb cb)
{
    int err;

    if (!(handle->flags & UV_HANDLE_POLL_SLOW)) {
        err = uv__fast_poll_set(handle->loop, handle, events);
    } else {
        err = uv__slow_poll_set(handle->loop, handle, events);
    }

    if (err) {
        return uv_translate_sys_error(err);
    }

    handle->poll_cb = cb;

    return 0;
}

int uv_poll_stop(uv_poll_t* handle)
{
    int err;

    if (!(handle->flags & UV_HANDLE_POLL_SLOW)) {
        err = uv__fast_poll_set(handle->loop, handle, 0);
    } else {
        err = uv__slow_poll_set(handle->loop, handle, 0);
    }

    return uv_translate_sys_error(err);
}

void uv_process_poll_req(uv_loop_t* loop, uv_poll_t* handle, uv_req_t* req)
{
    if (!(handle->flags & UV_HANDLE_POLL_SLOW)) {
        uv__fast_poll_process_poll_req(loop, handle, req);
    } else {
        uv__slow_poll_process_poll_req(loop, handle, req);
    }
}

int uv_poll_close(uv_loop_t* loop, uv_poll_t* handle)
{
    if (!(handle->flags & UV_HANDLE_POLL_SLOW)) {
        return uv__fast_poll_close(loop, handle);
    } else {
        return uv__slow_poll_close(loop, handle);
    }
}

void uv_poll_endgame(uv_loop_t* loop, uv_poll_t* handle)
{
    assert(handle->flags & UV__HANDLE_CLOSING);
    assert(!(handle->flags & UV_HANDLE_CLOSED));

    assert(handle->submitted_events_1 == 0);
    assert(handle->submitted_events_2 == 0);

    uv__handle_close(handle);
}
